/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.ui.widgets;

import java.util.Timer;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

//not tested with form
/**
 * Textbox widget for canvas based screens
 * <p>
 * 
 * You need to set the chars for each key (for the target device) in the
 * <code> getT9Chars() </code> and <code> getQwertyChars() </code> methods.<br/>
 * By default the Textbox does not have focus. To set or remove focus use the
 * <code>setFocus()</code> and <code>removeFocus()</code> methods respectively.
 * </p>
 * 
 * @author kentelt
 */
public final class TextBox extends TraversableWidget {

	private final class Ani extends java.util.TimerTask {
		public final void run() {
			System.out.println("Text box active threads " + Thread.activeCount());
			for (; hasFocus;) {
				try {
					Thread.sleep(caretBlinkDelay);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				reDrawWidget();
			}
		}
	}

	private static TextBox currTBinFocus;

	// // private int clearKeyCode = Integer.MIN_VALUE;
	// static com.aphrodite.util.log.Logger log =
	// com.aphrodite.util.log.LogManager.getLogger(TextBox.class,
	// com.aphrodite.util.log.ConsoleAppender.getAppender());

	public static final int TYPE_ANY = 20;
	public static final int TYPE_PASSWORD = 10;

	private static void changeFocus(TextBox tb) {
		if (currTBinFocus != null) {
			currTBinFocus.removeFocus();
		}
		currTBinFocus = tb;
	}

	private int caretBlinkDelay = 500;

	private boolean caretBlinkOn = true;
	private int caretColor = 0x000000;

	private int caretIndex = 0;
	private int caretLeft = 0;

	private int currentKeyStep = 0;
	private String currentString = new String();
	private StringBuffer currentText = new StringBuffer();
	private final int delChar = 8;// -8;
	private boolean goToNextChar = true;
	// private boolean isEditable = true;
	private volatile boolean hasFocus = false;

	// height of the input text
	private int inputHeight = 0;
	private int inputTranslationX = 0;
	private boolean isQwerty;
	private long lastCaretBlink = 0;
	private long lastKeyTimestamp = 0;
	private int lastPressedKey = Integer.MIN_VALUE;
	private long maxKeyDelay = 500L;
	private int maxLength;

	private int staticTextColor = 0xA0A0A0;

	/* Flag to set if backspace/arrow keys is used */
	private boolean stopBlinking;

	/* pad to start drawing the text from */
	private final int textPad = 3;

	private java.util.Timer timer;

	private final int type;

	public TextBox(Theme theme) {
		this(theme, 25, TYPE_ANY, true, 90);
	}

	/**
	 * @param theme
	 * @param maxSize
	 *            capacity in characters
	 * @param type
	 * @param isQwerty
	 *            keyboard
	 * @param width
	 *            of the textbox
	 */
	public TextBox(Theme theme, int maxSize, int type, boolean isQwerty, int width) {
		super(theme, width, 0);
		this.isQwerty = isQwerty;// (screenWidth >= 320) ? true : false;
		this.type = type;
		this.maxLength = maxSize--;
		updateModel();
	}

	/**
	 * Delete character left of the current caret position
	 */
	public void backSpace() {
		if (isEnabled) {
			stopBlinking = true;
			clearChar();
			updateCaretPosition();
			goToNextChar = true;
			reDrawWidget();
			stopBlinking = false;
		}
	}

	public void cleanUp() {
		hasFocus = false;
		currentText = null;
		currentString = null;
	}

	private void clearChar() {
		if (currentText.length() > 0 && caretIndex > 0) {
			caretIndex--;
			currentText.deleteCharAt(caretIndex);
			currentString = currentText.toString();
			setPasswordChars(true);
		}
	}

	public int getCaretPosition() {
		return caretIndex;
	}

	public void getChar(char[] data) {
		currentText.getChars(0, currentText.length(), data, 0);
	}

	// public char[] getChars(int key) {
	// switch (key) {
	// case Canvas.KEY_NUM1:
	// return KEY_NUM1_CHARS;
	// case Canvas.KEY_NUM2:
	// return KEY_NUM2_CHARS;
	// case Canvas.KEY_NUM3:
	// return KEY_NUM3_CHARS;
	// case Canvas.KEY_NUM4:
	// return KEY_NUM4_CHARS;
	// case Canvas.KEY_NUM5:
	// return KEY_NUM5_CHARS;
	// case Canvas.KEY_NUM6:
	// return KEY_NUM6_CHARS;
	// case Canvas.KEY_NUM7:
	// return KEY_NUM7_CHARS;
	// case Canvas.KEY_NUM8:
	// return KEY_NUM8_CHARS;
	// case Canvas.KEY_NUM9:
	// return KEY_NUM9_CHARS;
	// case Canvas.KEY_NUM0:
	// return KEY_NUM0_CHARS;
	// }
	// return null;
	// }

	private char[] getQwertyChars(int key) {
		switch (key) {
		case 113:
			return new char[] { 'q' };
		case 119:
			return new char[] { 'w' };
		case 101:
			return new char[] { 'e' };
		case 114:
			return new char[] { 'r' };
		case 116:
			return new char[] { 't' };
		case 121:
			return new char[] { 'y' };
		case 117:
			return new char[] { 'u' };
		case 105:
			return new char[] { 'i' };
		case 111:
			return new char[] { 'o' };
		case 112:
			return new char[] { 'p' };
		case 97:
			return new char[] { 'a' };
		case 115:
			return new char[] { 's' };
		case 100:
			return new char[] { 'd' };
		case 102:
			return new char[] { 'f' };
		case 103:
			return new char[] { 'g' };
		case 104:
			return new char[] { 'h' };
		case 106:
			return new char[] { 'j' };
		case 107:
			return new char[] { 'k' };
		case 108:
			return new char[] { 'l' };
		case 122:
			return new char[] { 'z' };
		case 120:
			return new char[] { 'x' };
		case 99:
			return new char[] { 'c' };
		case 118:
			return new char[] { 'v' };
		case 98:
			return new char[] { 'b' };
		case 110:
			return new char[] { 'n' };
		case 109:
			return new char[] { 'm' };
		case 10:
			return new char[] { '\n' };
		case 32:
			return new char[] { ' ' };
		case 35:
			return new char[] { '#' };
		case 40:
			return new char[] { '(' };
		case 41:
			return new char[] { ')' };
		case 95:
			return new char[] { '_' };
		case 45:
			return new char[] { '-' };
		case 43:
			return new char[] { '+' };
		case 64:
			return new char[] { '@' };
		case 47:
			return new char[] { '/' };
		case 58:
			return new char[] { ':' };
		case 59:
			return new char[] { ';' };
		case 39:
			return new char[] { '\'' };
		case 34:
			return new char[] { '"' };
		case 63:
			return new char[] { '?' };
		case 33:
			return new char[] { '!' };
		case 44:
			return new char[] { ',' };
		case 42:
			return new char[] { '*' };
		case 46:
			return new char[] { '.' };
		case 48:
			return new char[] { '0' };
		case 49:
			return new char[] { '1' };
		case 50:
			return new char[] { '2' };
		case 51:
			return new char[] { '3' };
		case 52:
			return new char[] { '4' };
		case 53:
			return new char[] { '5' };
		case 54:
			return new char[] { '6' };
		case 55:
			return new char[] { '7' };
		case 56:
			return new char[] { '8' };
		case 57:
			return new char[] { '9' };
		}
		return null;
	}

	public String getString() {
		return currentText.toString();
	}

	// this is for t9 based keyboards

	private char[] getT9Chars(int key) {
		switch (key) {
		case 49:
			return new char[] { 'e', 'r' };
		case 50:
			return new char[] { 't', 'y', };
		case 51:
			return new char[] { 'u', 'i' };
		case 52:
			return new char[] { 'd', 'f' };
		case 53:
			return new char[] { 'g', 'h' };
		case 54:
			return new char[] { 'j', 'k' };
		case 55:
			return new char[] { 'c', 'v' };
		case 56:
			return new char[] { 'b', 'n' };
		case 57:
			return new char[] { 'm' };
		case 48:
			return new char[] { ' ' };
		case 113:
			return new char[] { 'q', 'w' };
		case 111:
			return new char[] { 'o', 'p' };
		case 97:
			return new char[] { 'a', 's' };
		case 108:
			return new char[] { 'l' };
		case 122:
			return new char[] { 'z', 'x' };
		case 69:
			return new char[] { '1' };
		case 84:
			return new char[] { '2' };
		case 85:
			return new char[] { '3' };
		case 68:
			return new char[] { '4' };
		case 71:
			return new char[] { '5' };
		case 74:
			return new char[] { '6' };
		case 67:
			return new char[] { '7' };
		case 66:
			return new char[] { '8' };
		case 77:
			return new char[] { '9' };
		case 32:
			return new char[] { '0' };
		case 33:
			return new char[] { '!' };
		case 46:
			return new char[] { '.' };
		case 63:
			return new char[] { '?' };
		case 44:
			return new char[] { ',' };
		case 64:
			return new char[] { '@' };
		}
		return null;
	}

	/**
	 * Text would be inserted after the position specified
	 * 
	 * @param string
	 * @param position
	 */
	public void insert(String string, int position) {
		StringBuffer buf = new StringBuffer(currentText.toString());
		buf.insert(position, string);
		setString(buf.toString());
	}

	private boolean isClearKey(int key) {
		return key == delChar;
	}

	public boolean isEditable() {
		return isEnabled;
	}

	public boolean keyPressed(int gameAction, int key) {
		if (hasFocus) {
			if (key == javax.microedition.lcdui.Canvas.UP || key == javax.microedition.lcdui.Canvas.DOWN) {
				System.out.println("up/down");
				removeFocus();
				return false;
			} else if (isClearKey(key)) {
				backSpace();
			} else if (key != javax.microedition.lcdui.Canvas.LEFT && key != javax.microedition.lcdui.Canvas.RIGHT) {
				if (isEnabled)
					writeKeyPressed(key);
			} else if (gameAction == javax.microedition.lcdui.Canvas.LEFT) {
				moveLeft();
			} else if (gameAction == javax.microedition.lcdui.Canvas.RIGHT) {
				moveRight();
			}
			// else if (gameAction == javax.microedition.lcdui.Canvas.UP
			// || gameAction == javax.microedition.lcdui.Canvas.DOWN){
			// System.out.println("up/down")
			// removeFocus();
			//				
			// }
			return true;
		}
		return false;
	}

	// public final void moveDown() {
	// removeFocus();
	// }

	public final boolean moveLeft() {
		if (caretIndex > 0) {
			stopBlinking = true;
			caretIndex--;
			updateCaretPosition();
			goToNextChar = true;
			reDrawWidget();
			stopBlinking = false;
		}
		return true;
	}

	public final boolean moveRight() {
		if (caretIndex < currentText.length()) {
			stopBlinking = true;
			if (goToNextChar)
				caretIndex++;
			updateCaretPosition();
			goToNextChar = true;
			reDrawWidget();
			stopBlinking = false;
		}
		return true;

	}

	public boolean pointerPress(int px, int py) {
		if (isVisible && com.aphrodite.util.geom.GeomUtil.withinBounds(px, py, x, y, width, height)) {
			setFocus();
			return true;
		}
		return false;
	}

	protected void reDrawWidget() {
		/* If backspace/arrow keys is being pressed dont blink the cursor */
		if (!stopBlinking) {
			long currentTime = System.currentTimeMillis();
			if (lastCaretBlink + caretBlinkDelay < currentTime) {
				caretBlinkOn = !caretBlinkOn;
				lastCaretBlink = currentTime;
			}

			if (!goToNextChar && lastKeyTimestamp + maxKeyDelay < currentTime) {
				goToNextChar = true;
			}
		} else {
			caretBlinkOn = true;
		}
		super.reDrawWidget();
	}

	public void removeFocus() {
		System.out.println("removing text box focus");
		super.removeFocus();
		if (hasFocus) {
			hasFocus = false;
			timer.cancel();
			timer = null;
		}
		stateChanged(this);
		// isInFocus = false;
	}

	public void render(Graphics g) {
		if (!isVisible)
			return;
		capture(g, width, height, true);
		g.setFont(theme.widgetFont);
		g.translate(inputTranslationX, 0);
		g.setColor((isEnabled) ? theme.fgColor : staticTextColor);
		g.drawString(currentString, textPad, 2, 0);
		if (hasFocus && isEnabled) {
			if (caretBlinkOn && goToNextChar) {
				g.setColor(caretColor);
				g.fillRect(caretLeft + 1, 2, 2, inputHeight);
			}
		}
		g.translate(-inputTranslationX, 0);
		release(g, width, height, true);

	}

	/**
	 * Set the cursor blink rate.
	 * 
	 * @param delay
	 *            of cursor blink,The default is 500ms.
	 */
	public void setBlinkInterval(int delay) {
		this.caretBlinkDelay = delay;
		stateChanged(this);
	}

	public void setCaretColor(int rgb) {
		this.caretColor = rgb;
		stateChanged(this);
	}

	public void setChars(char[] data) {
		setString(new String(data));
		stateChanged(this);
	}

	public void setFocus() {
		super.setFocus();
		if (!hasFocus) {
			if (timer != null) {
				timer.cancel();
				timer = null;
			}
			TextBox.changeFocus(this);
			hasFocus = true;
			caretBlinkOn = true;
			timer = new Timer();
			timer.schedule(new Ani(), 0);
		}
		stateChanged(this);
	}

	public void setMaxSize(int maxSize) {
		this.maxLength = maxSize;
	}

	// public void setFont(javax.microedition.lcdui.Font font) {
	// super.setFont(font);
	// updateModel();
	// sizeChanged(width, height);
	// super.reDrawWidget();
	// }

	private void setPasswordChars(boolean isClearChar) {
		if (type == TYPE_PASSWORD) {
			StringBuffer buf = new StringBuffer(currentString);
			if (currentString.length() > 0) {
				int end = (isQwerty) ? currentString.length() : currentString.length() - 1;
				/*
				 * if backspace is pressed the previous char should not be
				 * visible
				 */
				if (isClearChar || !hasFocus)
					end = currentString.length();
				for (int i = 0; i < end; i++) {
					buf.deleteCharAt(i);
					buf.insert(i, '*');
				}
			}
			currentString = buf.toString();
		}
	}

	/**
	 * Set the text color if the text box is static
	 * 
	 * 
	 * @param color
	 *            default is grey.
	 */
	public void setStaticTextColor(int color) {
		this.staticTextColor = color;
	}

	/**
	 * 
	 * @param string
	 *            Even <b>null</b> is valid.
	 */
	public void setString(String string) {
		if (isEnabled) {
			currentText = (string != null) ? new StringBuffer(string) : new StringBuffer();
			currentString = currentText.toString();
			setPasswordChars(true);
			caretIndex = currentString.length();
			updateCaretPosition();
			stateChanged(this);
		}
	}

	public final int size() {
		return currentString.length() - 1;
	}

	protected final boolean traverse(int dir, int viewportWidth, int viewportHeight, int[] visRect_inout) {
		switch (dir) {
		case Canvas.DOWN:
			if (location == UPPER) {
				setFocus();
				location = IN;
			} else {
				removeFocus();
				return false;
			}
			break;
		case Canvas.UP:
			if (location == LOWER) {
				setFocus();
				location = IN;
			} else {
				removeFocus();
				return false;
			}
			break;
		case Canvas.LEFT:
			if (location == IN)
				moveLeft();
			break;
		case Canvas.RIGHT:
			if (location == IN)
				moveRight();
			break;
		}
		return true;
	}

	private void updateCaretPosition() {
		caretLeft = textPad + theme.widgetFont.substringWidth(currentString, 0, caretIndex);
		if (caretLeft + inputTranslationX < 0) {
			inputTranslationX = -caretLeft;
		} else if (caretLeft + inputTranslationX > width) {
			inputTranslationX = width - caretLeft;
		}
	}

	// /**
	// * write a character to the text box
	// *
	// * @param ch
	// */
	// public void writeString(String s) {
	//
	// if (isInFocus && isEditable) {
	// char[] test = s.toCharArray();
	// for (int i = 0; i < test.length; i++) {
	// writeKey(test[i]);
	// }
	// }
	// }

	// private void writeKey(char ch) {
	// if (goToNextChar) {
	// currentText.insert(caretIndex, ch);
	// caretIndex++;
	// } else {
	// currentText.setCharAt(caretIndex - 1, ch);
	// }
	// currentString = currentText.toString();
	// setPasswordChars(false);
	// updateCaretPosition();
	// lastKeyTimestamp = System.currentTimeMillis();
	// goToNextChar = false;
	// }

	protected void updateModel() {
		inputTranslationX = textPad;
		inputHeight = theme.widgetFont.getHeight();
		// top pad =2 ,bottom pad=2
		height = inputHeight + 4;
	}

	private void writeKeyPressed(int key) {
		if (currentString.length() < maxLength) {
			if (goToNextChar || key != lastPressedKey) {
				goToNextChar = true;
				lastPressedKey = key;
				currentKeyStep = 0;
			} else {
				currentKeyStep++;
			}
			char[] chars = isQwerty ? getQwertyChars(key) : getT9Chars(key);
			if (chars != null) {
				if (currentKeyStep >= chars.length) {
					currentKeyStep -= chars.length;
				}
				// writeKey(chars[currentKeyStep]);
				if (goToNextChar) {
					currentText.insert(caretIndex, chars[currentKeyStep]);
					caretIndex++;
				} else {
					currentText.setCharAt(caretIndex - 1, chars[currentKeyStep]);
				}
				currentString = currentText.toString();
				setPasswordChars(false);
				updateCaretPosition();
				lastKeyTimestamp = System.currentTimeMillis();
				goToNextChar = false;
			}
		}
		reDrawWidget();
	}
}
